package com.navinfo.tripanalysis.service;

import com.navinfo.tripanalysis.common.arithmetic.*;
import com.navinfo.tripanalysis.common.arithmetic.common.*;
import com.navinfo.tripanalysis.common.arithmetic.custom.VarFuelChange;
import com.navinfo.tripanalysis.common.arithmetic.common.TripInfo;
import com.navinfo.tripanalysis.common.arithmetic.manager.ArithmeticManager;
import com.navinfo.tripanalysis.common.config.ConfigMap;
import com.navinfo.tripanalysis.common.util.ArithmeticUtils;
import com.navinfo.tripanalysis.common.util.DateUtils;
import com.navinfo.tripanalysis.common.protocol.java.EventDataPb;
import com.navinfo.tripanalysis.pojo.AlgorithmChainCommand;
import com.navinfo.tripanalysis.pojo.Point;

import com.navinfo.tripanalysis.util.CommonUtils;
import com.navinfo.tripanalysis.util.IntegrateSupportUtil;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.*;

/**
 * 抽象的算法链服务
 * @author 沈东生
 */
@Data
public abstract class AbstractAlgorithmChainService implements Serializable {
    protected final Logger logger = LoggerFactory.getLogger(getClass());
    /**
     * 最小速度阀值
     */
    private int speedMin;
    /**
     * 最大速度阀值
     */
    private int speedMax;
    /**
     * 引擎最小转速阀值
     */
    private int rotationMin;
    /**
     * 引擎最大转速阀值
     */
    private int rotationMax;
    /**
     * 行程结束发动机转数低转时长
     */
    private int tripEndRotationTime;
    /**
     * 跨天的时间阀值，单位：秒<br/>
     * 距下一天多少秒内算行程未结束，算跨天行程。<br/>
     * 比如:设为30秒，则最后一个点的 gps时间为23:59:30到23:59:59 都算跨天行程
     */
    private long crossDayThresholdS;

    /**
     *  驾驶行为分析管理器
     */
    protected ArithmeticManager arithmeticManager;

    protected AbstractAlgorithmChainService(Map algorithmConfig){
        //初始化算法配置参数
        ConfigMap.init(algorithmConfig);
        this.setRotationMax(Integer.parseInt(ConfigMap.getString("rotation_max")));
        this.setRotationMin(Integer.parseInt(ConfigMap.getString("rotation_min")));
        this.setSpeedMax(Integer.parseInt(ConfigMap.getString("speed_max")));
        this.setSpeedMin(Integer.parseInt(ConfigMap.getString("speed_min")));
        this.setTripEndRotationTime(Integer.parseInt(ConfigMap.getString("trip_end_rotation_time")));
        this.setCrossDayThresholdS(Integer.parseInt(ConfigMap.getString("trip_crossDay_threshold_s")));

        arithmeticManager = new ArithmeticManager()
                .addArithmetic(new Mileage())
                .addArithmetic(new Gear())
                .addArithmetic(new Brake())
                .addArithmetic(new IdleParking())
                .addArithmetic(new IdleParking_DF())
                .addArithmetic(new DrivingSlope())
                .addArithmetic(new BrakeHand())
                .addArithmetic(new BrakeNormal())
                .addArithmetic(new BrakeHighSpeed())
                .addArithmetic(new CarStop())
                .addArithmetic(new CarStart())
                .addArithmetic(new CarIgnite())
                .addArithmetic(new CarFlameout())
                .addArithmetic(new CrossCity())
                .addArithmetic(new ClutchSwitch())
                .addArithmetic(new CruiseControl())
                .addArithmetic(new CoastInGear())
                .addArithmetic(new CoastNeutralGear())
                .addArithmetic(new DrivingEco())
                .addArithmetic(new DrivingCity())
                .addArithmetic(new DrivingFatigue())
                .addArithmetic(new FuelChange())
                .addArithmetic(new GasSharp())
                .addArithmetic(new GasSteady())
                .addArithmetic(new ParkingAccelerator())
                .addArithmetic(new RotationLow())
                .addArithmetic(new RotationOver())
                .addArithmetic(new RangeClock())
                .addArithmetic(new RangeSpeed())
                .addArithmetic(new RangeTorque())
                .addArithmetic(new RangeAltitude())
                .addArithmetic(new RangeRotation())
                .addArithmetic(new RangeWaterTemp())
                .addArithmetic(new RangeIntakeAirTemp())
                .addArithmetic(new RangeGasOpenPercent())
                .addArithmetic(new RangeIntakeAirPressure())
                .addArithmetic(new SpeedOver())
                .addArithmetic(new SpeedOver120())
                .addArithmetic(new SharpTurning())
                .addArithmetic(new SharpAcceleration())
                .addArithmetic(new SharpDeceleration())
                .addArithmetic(new TripPower())
                .addArithmetic(new VehicleEstimatedLoad())

                .addArithmetic(new CoastFlameout_DF())
                .addArithmetic(new CoastNeutralGear_DF())
                .addArithmetic(new DrivingCold_DF())
                .addArithmetic(new DrivingNight_DF())
                .addArithmetic(new GasLargeAndFull_DF())
                .addArithmetic(new IdleLong_DF())
                .addArithmetic(new RotationOver_DF())
                .addArithmetic(new SpeedFast_DF())
                .addArithmetic(new SharpTurning_DF())
                .addArithmetic(new SharpAcceleration_DF())
                .addArithmetic(new SharpDeceleration_DF())
                .addArithmetic(new VehicleLoadType_DF())
                .addArithmetic(new UreaInjection_DF())
                .addArithmetic(new UreaAbnormal_DF())
                .addArithmetic(new RangeRotationUrea_DF())
                .addArithmetic(new RangeSpeedUrea_DF())
                .addArithmetic(new RangeTorqueUrea_DF())
                .addArithmetic(new RangeGasOpenPercentUrea_DF());
    }

    /**
     * 计算行程和事件数据
     * @param command
     */
    public abstract void execute(AlgorithmChainCommand command);

    /**
     * 处理一个位置点数据
     * @param command      算法链执行的命令对象
     * @param drivingData 中间变量
     * @param point        点信息
     */
    protected void dealPoint(AlgorithmChainCommand command, DrivingData drivingData, Point point) {
        long tid = command.getTid();
        String tidStr = command.getTidStr();
        CommonData commonData = drivingData.getCommonData();

        long gpsTime = point.getGpsTime();
        long lastGpsTime = commonData.getLastGpsTime();
        int rotation = point.getRotation();
        int speed = (int) point.getSpeedKmh();
        int serialNumber = point.getSerialNumber();
        int lastSerialNumber = commonData.getLastSerialNumber();
        double totalOil = point.getTotalFuelCons();

        //数据不合法跳过
        if (!checkData(tid, lastGpsTime, gpsTime, rotation, speed, lastSerialNumber, serialNumber)) {
            return;
        }

        //如果中间变量中没有行程数据进行初始化
        if (drivingData.getTripInfo() == null || drivingData.getTripInfo().getStartTime()<=0) {
            startTrip(tidStr, drivingData, point);
        } else {
            commonData.setTripFirstNode(false);
        }

        //中间变量的公共缓存赋值
        IntegrateSupportUtil.assign(point, drivingData);

        //调用算法链计算相应指标（临时数据将发生变化）
        arithmeticManager.setDrivingData(drivingData).calculate();

        //为下次计算提供相关数据
        commonData.setLastGpsTime(gpsTime);
        commonData.setLastRotation(rotation);
        commonData.setLastSerialNumber(serialNumber);
        commonData.setLastSpeedMs(commonData.getSpeedMs());

        //将中间变量的事件数据，放到计算结果的事件数据中
        outPutEvents(drivingData, command, tid);
    }

    /**
     * 处理最后一个位置点数据
     * @param command
     * @param drivingData
     * @param point
     */
    protected void dealLastPoint(AlgorithmChainCommand command, DrivingData drivingData, Point point) {
        //如果中间变量中没有行程数据进行初始化
        if (drivingData.getTripInfo()==null || drivingData.getTripInfo().getStartTime() == 0) {
            startTrip(command.getTidStr(), drivingData, point);
        }

        CommonData commonData = drivingData.getCommonData();
        commonData.setTripLastNode(true);
        commonData.setTripFirstNode(false);

        long curGpsTime = point.getGpsTime();

        //与前一个点的gpstime不同
        if (curGpsTime - commonData.getGpsTime() > 0) {
            IntegrateSupportUtil.assign(point, drivingData);
        } else {
            //直接使用上一个点的drivingdata,再次计算
        }

        //调用算法链计算相应指标（临时数据将发生变化）
        arithmeticManager.setDrivingData(drivingData).calculate();

        //行程结束处理
        tripEnd(command, drivingData, curGpsTime);
    }

    /**
     * 处理跨天标志，当前的业务逻辑：一个行程是只允许跨一天。<br/>
     * 第一次跨天：昨天最后一个Trip的crossDay=0、DrivingData的crossDay=0，<br/>
     *             今天第一个Trip的crossDay=0<br/>
     *             今天最后一个Trip的crossDay=2、DrivingData的crossDay=2<br/>
     *
     * 昨天跨天：昨天最后一个Trip的crossDay=2、DrivingData的crossDay=2，<br/>
     *           今天第一个Trip的crossDay=1，<br/>
     *               如果今天就一个行程，最后一个Trip的crossDay=0、DrivingData的crossDay=0<br/>
     *               如果今天有多个行程，DrivingData的crossDay值和最后一个Trip的crossDay值相同<br/>
     * @param trips
     * @param drivingData 中间变量中存储的跨天标志是昨天的跨天标志
     */
    protected void handleCrossDay(List<OuterStatisticData> trips, DrivingData drivingData) {
        if (CommonUtils.isCollectionEmpty(trips)) {
            drivingData.setCrossDayFlag(0);
            return;
        }

        OuterStatisticData firstTrip = trips.get(0);
        OuterStatisticData lastTrip = trips.get(trips.size() - 1);

        //昨天最后一个行程的跨天标志
        int preStatus = drivingData.getCrossDayFlag();
        //今天最后一个行程的跨天标志
        int lastTripStatus = lastTrip.getCrossDayFlag();

       /* 第一次跨天：昨天最后一个Trip的crossDay=0、DrivingData的crossDay=0
        *             今天第一个Trip的crossDay=0<br/>
        *             今天最后一个Trip的crossDay=2、DrivingData的crossDay=2<br/>
        */
        if (preStatus == 0 ) {
            firstTrip.setCrossDayFlag(0);
            if (lastTripStatus == 2) {
                drivingData.setCrossDayFlag(2);

                //防止第一个行程和最后一个行程是同一个，重新赋值
                lastTrip.setCrossDayFlag(2);
            }


        }
        /*昨天跨天：昨天最后一个Trip的crossDay=2、DrivingData的crossDay=2，<br/>
         *          今天第一个Trip的crossDay=1，<br/>
         *               如果今天就一个行程，最后一个Trip的crossDay=0、DrivingData的crossDay=0<br/>
         *               如果今天有多个行程，DrivingData的crossDay值和最后一个Trip的crossDay值相同<br/>
         */
        else {
            //今天的第一条行程设置跨天标识
            firstTrip.setCrossDayFlag(1);

            //今天就一个行程
            if (firstTrip==lastTrip) {
                //强制最后一个点的跨天标志位0
                lastTrip.setCrossDayFlag(0);
                drivingData.setCrossDayFlag(0);
            }
            //今天有多个行程，DrivingData的crossDay值和最后一个Trip的crossDay值相同
            else{
                drivingData.setCrossDayFlag(lastTripStatus);
            }
        }
    }

    /**
     * 收集并补充行程结束信息
     * @param drivingData 中间变量
     * @param tid           通信号
     * @return 行程
     */
    protected OuterStatisticData collectTripData(DrivingData drivingData, long tid, Map<Long, Integer> tileLcMapping) {
        OuterStatisticData outerStatisticData = new OuterStatisticData();

        //循环调用算法统计结果
        arithmeticManager.setDrivingData(drivingData).collect(outerStatisticData);

        //行程数据赋值
        TripInfo tripInfo = drivingData.getTripInfo();
        outerStatisticData.setTid(tid);
        outerStatisticData.setUid(tid);
        outerStatisticData.setForceEndFlag(0);
        outerStatisticData.setRouteStopFlag(1);
        outerStatisticData.setDay(DateUtils.format(new Date(tripInfo.getStartTime()), DateUtils.DateFormat.YYYYMMDD));

        //使用行程的数据赋值，之后会在TripAnalysisApplication中使用0200数据精准
        outerStatisticData.setRouteStartTime(tripInfo.getStartTime() / 1000);
        outerStatisticData.setRouteStartLatitude(tripInfo.getStartLatitude());
        outerStatisticData.setRouteStartLongitude(tripInfo.getStartLongitude());
        outerStatisticData.setRouteStartHeight(tripInfo.getStartHeight());
        outerStatisticData.setRouteEndTime(tripInfo.getEndTime() / 1000);
        outerStatisticData.setRouteStartDirection(tripInfo.getStartDirection());
        outerStatisticData.setRouteEndLongitude(tripInfo.getEndLongitude());
        outerStatisticData.setRouteEndLatitude(tripInfo.getEndLatitude());
        outerStatisticData.setRouteEndHeight(tripInfo.getEndHeight());
        outerStatisticData.setRouteEndDirection(tripInfo.getEndDirection());
        outerStatisticData.setTripDuration((int) (outerStatisticData.getRouteEndTime() - outerStatisticData.getRouteStartTime()));

        //设置行程的里程，油耗，后面还会在TripAnalysisApplication中精准
        outerStatisticData.setTripFuel(outerStatisticData.getRouteIntegralFuel());
        outerStatisticData.setTripMileage(outerStatisticData.getRouteIntegralMileage());

        //根据车辆的经纬度，补充对应的行程开始结束的区域数据，后面还会在TripAnalysisApplication中精准
        if (tileLcMapping != null) {
            outerStatisticData.setRouteStartLc(ArithmeticUtils.getLc(outerStatisticData.getRouteStartLatitude(), outerStatisticData.getRouteStartLongitude(), tileLcMapping));
            outerStatisticData.setRouteEndLc(ArithmeticUtils.getLc(outerStatisticData.getRouteEndLatitude(), outerStatisticData.getRouteEndLongitude(), tileLcMapping));
        }


        return outerStatisticData;
    }

    /**
     * 收集补充基础的事件相关数据
     * @param command
     * @param tid
     * @param eventData
     */
    protected void collectEventData(AlgorithmChainCommand command, long tid, OuterEventData eventData) {
        eventData.setTid(tid);
        eventData.setUid(tid);
        eventData.setDay(DateUtils.format(command.getDay(), DateUtils.DateFormat.YYYYMMDD));
    }

    /**
     * 行程开始处理，中间变量放入行程数据
     * @param tid         通信号
     * @param drivingData 中间变量
     * @param point       0F37轨迹点
     * @return 开始或正在进行中返回true
     */
    private void startTrip(String tid, DrivingData drivingData, Point point) {
        logger.info("===行程开始! 通信号：{} ===", tid);

        //处理CommonData
        CommonData commonData = drivingData.getCommonData();
        commonData.clearData();
        commonData.setTerminalId(tid);
        commonData.setTripFirstNode(true);

        TripInfo tripInfo = new TripInfo();
        tripInfo.setStartTime(point.getGpsTime());
        tripInfo.setStartHeight((int) point.getHeight());
        tripInfo.setStartLongitude(point.getLongitude());
        tripInfo.setStartLatitude(point.getLatitude());
        tripInfo.setStartDirection(point.getDirection());
        drivingData.setTripInfo(tripInfo);
    }

    /**
     * 行程结束处理
     * @param command     算法链执行的命令对象
     * @param drivingData 中间变量
     * @param curGpsTime 最后一个点的gps时间
     */
    private void tripEnd(AlgorithmChainCommand command, DrivingData drivingData, long curGpsTime) {
        TripInfo tripInfo = drivingData.getTripInfo();
        if (tripInfo == null) {
            return;
        }

        long tid = command.getTid();
        CommonData commonData = drivingData.getCommonData();
        tripInfo.setEndTime(commonData.getGpsTime());
        tripInfo.setEndHeight(commonData.getHeight());
        tripInfo.setEndLongitude(commonData.getLongitude());
        tripInfo.setEndLatitude(commonData.getLatitude());
        tripInfo.setEndDirection(commonData.getGpsDirection());

        //收集并补充行程结束信息
        OuterStatisticData statisticData = collectTripData(drivingData, tid, drivingData.getTileLcMapping());

        //获取事件列表
        List<OuterEventData> eventList = drivingData.getOuterEvent();
        if (eventList == null) {
            eventList = new ArrayList<>();
        }

        boolean crossDay = false;
        if (command.isHandleCrossDay()) {
            //行程是否跨天的判断：最后一个位置点的时间到第二天0点的时间间隔<阀值，为跨天行程；
            crossDay = ((DateUtils.addDays(command.getDay(), 1).getTime()-curGpsTime) <= crossDayThresholdS*1000);
        }

        if (!crossDay) {
            statisticData.setCrossDayFlag(0);
            //未跨天的行程，强制结束未结束事件
            Map<EventDataPb.EventType, OuterEventData> eventDataMap = drivingData.getEventData();
            for (Map.Entry<EventDataPb.EventType, OuterEventData> entry : eventDataMap.entrySet()) {
                OuterEventData event = entry.getValue();
                event.setStopGpsDirection(tripInfo.getEndDirection());
                event.setStopGpsTime(tripInfo.getEndTime());
                event.setStopHeight(tripInfo.getEndHeight());
                event.setStopLatitude(tripInfo.getEndLatitude());
                event.setStopLongitude(tripInfo.getEndLongitude());
                event.setStopSpeed(commonData.getSpeedKmh());
                event.setDuration(tripInfo.getEndTime() - event.getStartGpsTime());
                eventList.add(event);
            }
            drivingData.setEventData(new HashMap<>(16));
        } else {
            //行程跨天标识
            statisticData.setCrossDayFlag(2);
        }

        //赋值计算结果的行程数据
        command.getOuterData().getOutStatistics().add(statisticData);
        logger.info("===已将结束的行程放入返回列表中! 通信号：{} ===", tid);

        //赋值计算结果的事件数据
        outPutEvents(drivingData, command, tid);

        //重置中间变量的值
        drivingData.setTripInfo(null);
        drivingData.setOuterEvent(new ArrayList<>());
        drivingData.setOuterStatistic(new HashMap<>());

        //行程结束，记录现在的油液情况用于下次行程开始时计算加油漏油
        VarFuelChange varFuelChange = new VarFuelChange();
        Double oilPer = commonData.getOilPercentage();
        if (oilPer != null) {
            varFuelChange.setLastTripOilPer(oilPer);
        }
        Double totalOil = commonData.getTotalOil();
        if (totalOil != null) {
            varFuelChange.setLastTotalOil(totalOil);
        }
        varFuelChange.setLastGpsTime(commonData.getGpsTime());
        drivingData.getOuterStatistic().put(FuelChange.class.getSimpleName(), varFuelChange);

        //行程结束，初始化停车通电相关参数，用于下次行程开始时计算停车通电时长等信息
        //只适用于流计算，离线计算不包含行程外的位置点
        //VarParkingPowerOn varParkingPowerOn = new VarParkingPowerOn();
        //varParkingPowerOn.setParkingTime(tripInfo.getEndTime());
        //drivingData.getOuterStatistic().put(ParkingPowerOn.class.getSimpleName(), varParkingPowerOn);

        logger.info("===行程结束! 通信号：{} ===", tid);
    }

    /**
     * 将中间变量的事件数据，放到计算结果的事件数据中
     * @param drivingData
     * @param command
     * @param tid
     */
    private void outPutEvents(DrivingData drivingData, AlgorithmChainCommand command, long tid) {
        for (OuterEventData eventData : drivingData.getOuterEvent()) {
            collectEventData(command, tid, eventData);
            List<OuterEventData> outEvents = command.getOuterData().getOutEvents();
            if (outEvents != null) {
                outEvents.add(eventData);
            } else {
                logger.info("不输出事件数据{}", tid);
            }
        }

        //清除中间变量的事件数据
        drivingData.getOuterEvent().clear();
    }

    /**
     * 校验数据合法性
     * @param tid             通信号
     * @param lastGpsTime     上个点时间
     * @param curGpsTime 当前点时间
     * @param rotation        转速
     * @param speed           速度
     * @return 是否合法
     */
    private boolean checkData(long tid, long lastGpsTime, long curGpsTime, int rotation, int speed, int lastSerialnumber, int serialnumber) {
        //本次gps时间小于等于上个点的gps时间，不予处理
        if (curGpsTime <= lastGpsTime) {
            logger.info("===本次gps时间小于等于上个点的gps时间，不予处理! (本次:{},上次：{}) 通信号：{} ===", curGpsTime, lastGpsTime, tid);
            return false;
        }

        //发动机转速不在合理区间内
        if (rotation < rotationMin || rotation > rotationMax) {
            logger.info("===发动机转速不在合理区间内，不予处理!（转速：" + rotation + "rpm）通信号：{} ===", tid);
            return false;
        }

        //速度不在合理区间内
        if (speed < speedMin || speed > speedMax) {
            logger.info("===速度不在合理区间内，不予处理!（速度：" + speed + "km/h）通信号：{} ===", tid);
            return false;
        }

        return true;
    }
}
